home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1999 July: Mac OS SDK / Dev.CD Jul 99 SDK1.toast / Development Kits / Mac OS / QuickDraw3D 1.6 SDK / Mac SampleCode New for 1.6 / WorldRayPickSample / Source / WRay_Pick.c < prev    next >
Encoding:
C/C++ Source or Header  |  1999-05-20  |  23.3 KB  |  991 lines  |  [TEXT/CWIE]

  1. /* 
  2.  *    WRay_Pick.c
  3.  *
  4.  *    QuickDraw 3D 1.6 Sample
  5.  *    Robert Dierkes
  6.  *
  7.  *    This sample code simpulates a bouncing ball.  It is a simplification
  8.  *    of this, however, in that it only tests a single ray against neighboring
  9.  *    objects for collisions.  The world ray pick origin is positioned at the
  10.  *    center of the ball and the direction vector controls is direction.
  11.  *    Sometimes portions of the animated ball pass through objects because
  12.  *    the hit testing is essentially done from the front of the ball and not
  13.  *    several points along its surface.
  14.  *
  15.  *     07/28/98    RDD        Created.
  16.  */
  17.  
  18. /*------------------*/
  19. /*    Include Files    */
  20. /*------------------*/
  21. #include "QD3D.h"
  22. #include "QD3DMath.h"
  23. #include "QD3DGroup.h"
  24. #include "QD3DPick.h"
  25. #include "QD3DTransform.h"
  26. #include "QD3DView.h"
  27.  
  28. #include "WRay_Error.h"
  29. #include "WRay_Document.h"
  30. #include "WRay_Memory.h"
  31. #include "WRay_Scene.h"
  32. #include "WRay_System.h"
  33. #include "WRay_Pick.h"
  34.  
  35. #include <math.h>
  36.  
  37.  
  38.  
  39. /*------------------*/
  40. /*      Constants        */
  41. /*------------------*/
  42. #define    kBallRadius                (kCylRadius * 0.95f)
  43. #define    kMinimumDistance        (kBallRadius * 0.956f)
  44.  
  45. #define    kFirstHit                0
  46.  
  47. /*------------------*/
  48. /*      Macros        */
  49. /*------------------*/
  50. /* ValidMask Macros */
  51. #define    HAS_Object(validMask)    ((validMask & kQ3PickDetailMaskObject)    !=0)
  52. #define    HAS_Distance(validMask)    ((validMask & kQ3PickDetailMaskDistance)!=0)
  53. #define    HAS_XYZ(validMask)         ((validMask & kQ3PickDetailMaskXYZ)!=0)
  54. #define    HAS_Normal(validMask)     ((validMask & kQ3PickDetailMaskNormal)!=0)
  55.  
  56.  
  57. /*----------------------*/
  58. /*    Global Declarations    */
  59. /*----------------------*/
  60. static    TQ3PickObject        gPick = NULL;
  61. static    TQ3Ray3D            gWorldRay;
  62. static    TQ3Vector3D            gWorldDelta;
  63.  
  64. static    TQ3GroupPosition    gBallGroupPosn    = NULL;
  65. static    TQ3GeometryObject    gBallGeo = NULL;
  66. static    TQ3GeometryObject    gCurGeoHit    = NULL;
  67. static    TQ3GroupObject        gLineGroup = NULL;
  68.  
  69. TQ3ColorRGB    rgbColors[] = {
  70.             {    1.0,    1.0,    1.0    },    /* k_White    */
  71.             {    1.0,    0.0,    0.0    },    /* k_Red    */
  72.             {    0.0,    1.0,    0.0    },    /* k_Green    */
  73.             {    0.0,    0.0,    1.0    },    /* k_Blue    */
  74.             {    1.0,    1.0,    0.0    },    /* k_Yellow    */
  75.             {    0.0,    1.0,    1.0    },    /* k_Cyan    */
  76.             {    1.0,    0.0,    1.0    },    /* k_Magenta*/
  77.             {    1.0,    0.5,    0.0    }    /* k_Orange    */
  78.         };
  79.  
  80. enum {
  81.     k_White,
  82.     k_Red,
  83.     k_Green,
  84.     k_Blue,
  85.     k_Yellow,
  86.     k_Cyan,
  87.     k_Magenta,
  88.     k_Orange
  89. };
  90.  
  91.  
  92. /*----------------------*/
  93. /*    Local Prototypes    */
  94. /*----------------------*/
  95. static
  96. TQ3PickObject Pick_New(
  97.             TQ3Ray3D            *pWorldRay);
  98. static
  99. TQ3Status Pick_Document(
  100.             TDocumentPtr        pDoc,
  101.             TQ3PickObject        pickObj,
  102.             unsigned long         *pNumHits);
  103.  
  104. static
  105. TQ3Status Pick_InitializeWorldRay(
  106.             TQ3PickObject        pickObj,
  107.             TQ3Ray3D            *pWorldRay,
  108.             TQ3Vector3D            *pWorldDelta);
  109. static
  110. TQ3Status Pick_ComputeReflectedWorldRay(
  111.             TQ3PickObject        pickObj,
  112.             TQ3Ray3D            *pWorldRay,
  113.             TQ3Vector3D            *pWorldDelta);
  114. static
  115. TQ3GeometryObject Pick_CreateMovingObject(
  116.             TDocumentPtr        pDoc,
  117.             TQ3Ray3D            *pRay,
  118.             TQ3GroupPosition    *pGroupPosn);
  119. static
  120. TQ3Status Pick_GetNearestObject(
  121.             TQ3PickObject        pickObj,
  122.             float                minDistance,
  123.             TQ3GeometryObject    *pGeoHit,
  124.             TQ3GeometryObject    *pPrevGeoHit);
  125. static
  126. TQ3Status Pick_HighlightObject(
  127.             TDocumentPtr        pDoc,
  128.             TQ3GeometryObject    geoObj,
  129.             TQ3Boolean            doHighlight);
  130.  
  131. TQ3GeometryObject Pick_CreateLine(
  132.             TQ3Point3D            *pXYZ,
  133.             TQ3Vector3D            *pNormal,
  134.             float                scale,
  135.             unsigned long        colorIndex);
  136.  
  137. TQ3Status Pick_DrawRayAndNormal(
  138.             TDocumentPtr        pDoc,
  139.             TQ3Ray3D            *pTestRay,
  140.             TQ3GroupObject        normalGroup,
  141.             unsigned long        numHits);
  142.  
  143.  
  144. /*
  145.  *    Pick_Initialize
  146.  */
  147. TQ3Boolean Pick_Initialize(
  148.     void)
  149. {
  150.     TQ3Status    status = kQ3Failure;
  151.  
  152.     gPick            = NULL;
  153.     gBallGroupPosn    = NULL;
  154.     gBallGeo        = NULL;
  155.     gCurGeoHit        = NULL;
  156.  
  157.     Q3Point3D_Set(&gWorldRay.origin,     0.0, 0.0, 0.0);
  158.     Q3Vector3D_Set(&gWorldRay.direction, 0.0, 0.0, 1.0);
  159.  
  160.     gPick = Pick_New(&gWorldRay);
  161.  
  162.     return (gPick != NULL) ? kQ3True : kQ3False;
  163. }
  164.  
  165.  
  166. /*
  167.  *    Pick_Exit
  168.  */
  169. TQ3Boolean Pick_Exit(
  170.     void)
  171. {
  172.     Object_Dispose_NULL(&gPick);
  173.     Object_Dispose_NULL(&gBallGeo);
  174.     Object_Dispose_NULL(&gCurGeoHit);
  175.     Object_Dispose_NULL(&gLineGroup);
  176.  
  177.     return kQ3True;
  178. }
  179.  
  180.  
  181. #pragma mark -
  182.  
  183. /*
  184.  *    Pick_New
  185.  */
  186. TQ3PickObject Pick_New(
  187.     TQ3Ray3D            *pWorldRay)
  188. {
  189.     TQ3WorldRayPickData    rayPickData;
  190.     TQ3PickObject        pickObject = NULL;
  191.  
  192.     /*
  193.      * Tolerance values are only used for Point, Line, Ellipse,
  194.      * NURB Curve, PolyLine, Mesh geometries and are measured
  195.      * in world space.
  196.      */
  197.     #define    kVertexTolerance    0.01
  198.     #define    kEdgeTolerance        0.005
  199.  
  200.     rayPickData.data.sort = kQ3PickSortNearToFar;
  201.  
  202.     rayPickData.data.mask = kQ3PickDetailMaskObject        |
  203.                             kQ3PickDetailMaskXYZ        |
  204.                             kQ3PickDetailMaskDistance    |
  205.                             kQ3PickDetailMaskNormal;
  206.     rayPickData.data.numHitsToReturn = 1;
  207.  
  208.     /* Make sure ray is normalized */
  209.     Q3Vector3D_Normalize(&pWorldRay->direction, &pWorldRay->direction);
  210.     rayPickData.ray = *pWorldRay;
  211.  
  212.     rayPickData.vertexTolerance    = kVertexTolerance;
  213.     rayPickData.edgeTolerance    = kEdgeTolerance;
  214.  
  215.     /* Create the new world ray pick object */
  216.     pickObject = Q3WorldRayPick_New(&rayPickData);
  217.     DEBUG_ASSERT(pickObject != NULL, Q3WorldRayPick_New);
  218.  
  219.     return pickObject;
  220. }
  221.  
  222.  
  223. /*
  224.  *    Pick_Document
  225.  */
  226. static
  227. TQ3Status Pick_Document(
  228.     TDocumentPtr        pDoc,
  229.     TQ3PickObject        pickObj,
  230.     unsigned long         *pNumHits)
  231. {
  232.     TQ3Status        status = kQ3Success;
  233.     TQ3ViewStatus    viewStatus = kQ3ViewStatusError;
  234.  
  235.     if (Q3View_StartPicking(pDoc->fView, pickObj) == kQ3Failure) {
  236.         ERROR_DEBUG_STR("Pick_Document: Q3View_StartPicking failed.");
  237.         return kQ3Failure;
  238.     }
  239.  
  240.     do {
  241.         Document_Submit_Objects(pDoc, pDoc->fView);
  242.  
  243.         viewStatus = Q3View_EndPicking(pDoc->fView);
  244.     }
  245.     while (viewStatus == kQ3ViewStatusRetraverse);
  246.  
  247.     DEBUG_ASSERT(viewStatus == kQ3ViewStatusDone, Pick_Document);
  248.  
  249.     status = Q3Pick_GetNumHits(pickObj, pNumHits);
  250.  
  251.     return status;
  252. }
  253.  
  254.  
  255. #pragma mark -
  256.  
  257. /*
  258.  *    Pick_GetIsAnimating
  259.  */
  260. TQ3Boolean Pick_GetIsAnimating(
  261.     void)
  262. {
  263.     return (gBallGeo != NULL) ? kQ3True : kQ3False;
  264. }
  265.  
  266.  
  267. /*
  268.  *    Pick_SetIsAnimating
  269.  */
  270. TQ3Status Pick_SetIsAnimating(
  271.     TDocumentPtr    pDoc,
  272.     TQ3Boolean        newIsAnimating)
  273. {
  274.     if (newIsAnimating == Pick_GetIsAnimating()) {
  275.         return kQ3Failure;
  276.     }
  277.  
  278.     return (newIsAnimating)    ? Pick_BeginAnimation(pDoc)
  279.                             : Pick_EndAnimation(pDoc);
  280. }
  281.  
  282.  
  283. #pragma mark -
  284.  
  285. /*
  286.  *    Pick_BeginAnimation
  287.  */
  288. TQ3Status Pick_BeginAnimation(
  289.     TDocumentPtr        pDoc)
  290. {
  291.     TQ3Status            status = kQ3Failure;
  292.  
  293.     if (Pick_GetIsAnimating() == kQ3True) {
  294.         /* Animation already on */
  295.         return status;
  296.     }
  297.  
  298.     /* Setup initial world ray origin and direction */
  299.     status = Pick_InitializeWorldRay(gPick, &gWorldRay, &gWorldDelta);
  300.     if (status == kQ3Failure) {
  301.         return status;
  302.     }
  303.  
  304.     /* Create ball */
  305.     gBallGeo = Pick_CreateMovingObject(pDoc, &gWorldRay, &gBallGroupPosn);
  306.     DEBUG_ASSERT(gBallGeo != NULL, Pick_CreateMovingObject);
  307.     if (gBallGeo == NULL) {
  308.         return kQ3Failure;
  309.     }
  310.  
  311.     gLineGroup = Q3OrderedDisplayGroup_New();
  312.     DEBUG_ASSERT(gLineGroup != NULL, Q3OrderedDisplayGroup_New);
  313.  
  314.     return status;
  315. }
  316.  
  317.  
  318. /*
  319.  *    Pick_EndAnimation
  320.  */
  321. TQ3Status Pick_EndAnimation(
  322.     TDocumentPtr        pDoc)
  323. {
  324.     if (Pick_GetIsAnimating() == kQ3False) {
  325.         /* Animation not on */
  326.         return kQ3Failure;
  327.     }
  328.  
  329.     Object_Dispose_NULL(&gLineGroup);
  330.  
  331.     /* Unhighlight anything that's still highlighted */
  332.     if (gCurGeoHit != NULL) {
  333.         /* Unhighlight the ball & current geometry */
  334.         Pick_HighlightObject(pDoc, gBallGeo, kQ3False);
  335.         Pick_HighlightObject(pDoc, gCurGeoHit, kQ3False);
  336.         Object_Dispose_NULL(&gCurGeoHit);
  337.     }
  338.     Object_Dispose_NULL(&gBallGeo);
  339.  
  340.     /* Remove temporary ball group from main group */
  341.     if (gBallGroupPosn != NULL) {
  342.         DEBUG_ASSERT(gCurGeoHit == NULL, Pick_EndAnimation);
  343.         gCurGeoHit = Q3Group_RemovePosition (pDoc->fModel, gBallGroupPosn);
  344.         Object_Dispose_NULL(&gCurGeoHit);
  345.         gBallGroupPosn = NULL;
  346.     }
  347.  
  348.     Document_Draw(pDoc);
  349.  
  350.     return kQ3Success;
  351. }
  352.  
  353.  
  354. /*
  355.  *    Pick_Animate
  356.  *
  357.  *    Move ball through the scene avoiding objects using
  358.  *    ray picking to check for geometries in its path.
  359.  */
  360. TQ3Status Pick_Animate(
  361.     TDocumentPtr        pDoc)
  362. {
  363.     TQ3Status            status    = kQ3Failure;
  364.     unsigned long        numHits;
  365.     TQ3GeometryObject    prevGeoHit    = NULL;
  366.  
  367.     if (Pick_GetIsAnimating() == kQ3False) {
  368.         /* If animation hasn't started then do nothing */
  369.         return kQ3Failure;
  370.     }
  371.  
  372.     /* Submit scene for picking */
  373.     status = Pick_Document(pDoc, gPick, &numHits);
  374.     DEBUG_ASSERT(status == kQ3Success, Pick_Document);
  375.  
  376.     if (numHits > 0) {
  377.         status = Pick_GetNearestObject(gPick, kMinimumDistance, &gCurGeoHit, &prevGeoHit);
  378.  
  379.         if (Scene_GetShowLines()) {
  380.             Pick_DrawRayAndNormal(pDoc, &gWorldRay, gLineGroup, numHits);
  381.         }
  382.  
  383.         if (gCurGeoHit != prevGeoHit) {
  384.             if (prevGeoHit != NULL) {
  385.                 /* Unhighlight the ball & previous geometry */
  386.                 Pick_HighlightObject(pDoc, gBallGeo, kQ3False);
  387.                 Pick_HighlightObject(pDoc, prevGeoHit, kQ3False);
  388.                 Object_Dispose_NULL(&prevGeoHit);
  389.             }
  390.  
  391.             if (gCurGeoHit != NULL) {
  392.                 /* Highlight the ball & the new closest geometry*/
  393.                 Pick_HighlightObject(pDoc, gBallGeo, kQ3True);
  394.                 Pick_HighlightObject(pDoc, gCurGeoHit, kQ3True);
  395.  
  396.                 /* status = */
  397.                 Pick_ComputeReflectedWorldRay(gPick, &gWorldRay, &gWorldDelta);
  398.  
  399.                 System_Sound();
  400.             }
  401.         }
  402.     }
  403.     else {
  404.         /* Nothing hit */
  405.         if (gCurGeoHit != NULL) {
  406.             /* Unhighlight the ball & current geometry */
  407.             Pick_HighlightObject(pDoc, gBallGeo, kQ3False);
  408.             Pick_HighlightObject(pDoc, gCurGeoHit, kQ3False);
  409.             Object_Dispose_NULL(&gCurGeoHit);
  410.         }
  411.     }
  412.  
  413.     Q3Pick_EmptyHitList(gPick);
  414.  
  415.     /* Advance ball by delta amount */
  416.     Q3Point3D_Vector3D_Add(&gWorldRay.origin, &gWorldDelta, &gWorldRay.origin);
  417.     Q3Ellipsoid_SetOrigin(gBallGeo, &gWorldRay.origin);
  418.  
  419.     /* Set ray again */
  420.     status = Q3WorldRayPick_SetRay(gPick, &gWorldRay);
  421.     DEBUG_ASSERT(status == kQ3Success, Q3WorldRayPick_SetRay);
  422.  
  423.     return status;
  424. }
  425.  
  426.  
  427. #pragma mark -
  428.  
  429. /*
  430.  *    Pick_InitializeWorldRay
  431.  *
  432.  *    Create an world delta vector with a random direction with a
  433.  *    magnitude that controls the rate.  Normalize this vector to
  434.  *    make the actual world ray. The world ray is initially positioned
  435.  *    at the origin.
  436.  */
  437. static
  438. TQ3Status Pick_InitializeWorldRay(
  439.     TQ3PickObject        pickObj,
  440.     TQ3Ray3D            *pWorldRay,
  441.     TQ3Vector3D            *pWorldDelta)
  442. {
  443.     TQ3Status            status = kQ3Failure;
  444.  
  445.     Q3Point3D_Set(&pWorldRay->origin, 0.0, 0.0, 0.0);
  446.  
  447.     /* Generate a random vector in XY plane */
  448.     Q3Vector3D_Set(pWorldDelta, System_RandomFloat(), System_RandomFloat(), 0.0);
  449.  
  450.     Q3Vector3D_Normalize(pWorldDelta, &pWorldRay->direction);
  451.  
  452.     /* Set world ray's origin and direction */
  453.     status = Q3WorldRayPick_SetRay(pickObj, pWorldRay);
  454.     DEBUG_ASSERT(status == kQ3Success, Q3WorldRayPick_SetRay);
  455.  
  456.     return status;
  457. }
  458.  
  459.  
  460. /*
  461.  *    Pick_ComputeReflectedWorldRay
  462.  *
  463.  *    Given:    pWorldDelta
  464.  *            pRay->direction (a normalized pWorldDelta)
  465.  *            normal
  466.  *
  467.  *    Find:    A new pWorldDelta with a negative angle relative to the angle
  468.  *            between delta and normal.
  469.  *
  470.  *            ++
  471.  *           /!|\
  472.  *          / !| \
  473.  *         / N!|  \
  474.  *      D /   !|   \ D'
  475.  *       /    V|    \
  476.  *      /      |     \
  477.  *     /     D1|      \
  478.  *    v<-----  V ----->v
  479.  *        D2     D2'
  480.  *
  481.  *    !    N  = Normal
  482.  *    /    D  = Delta
  483.  *    |    D1 = Delta component
  484.  *    -    D2 = Delta component
  485.  *    \    D' = Reflection of D
  486.  *        c  = scalar for D projection onto N
  487.  *
  488.  *            D • N
  489.  *        c = -----    but since N • N = 1 then    c = D • N
  490.  *            N • N
  491.  *
  492.  *        D  = D1 + D2
  493.  *        D1 = cN
  494.  *
  495.  *        D  = cN + D2    therefore
  496.  *        D2 = D - cN
  497.  *
  498.  *        D2' = Negate(D2)
  499.  *        D' = D1 + D2'
  500.  */
  501. static
  502. TQ3Status Pick_ComputeReflectedWorldRay(
  503.     TQ3PickObject        pickObj,
  504.     TQ3Ray3D            *pWorldRay,
  505.     TQ3Vector3D            *pWorldDelta)
  506. {
  507.     TQ3Status            status;
  508.     TQ3Vector3D            normal,
  509.                         newDelta,
  510.                         delta1,
  511.                         delta2;
  512.     float                c;
  513.  
  514.     status = Q3Pick_GetPickDetailData (pickObj, kFirstHit,
  515.                 kQ3PickDetailMaskNormal, &normal);
  516.     DEBUG_ASSERT(status == kQ3Success, Q3Pick_GetPickDetailData);
  517.     if (status == kQ3Failure) {
  518.         return status;
  519.     }
  520.  
  521.     /* We negate pWorldDelta to reverse it's direction giving us the newDelta */
  522.     Q3Vector3D_Negate(pWorldDelta, &newDelta);
  523.  
  524.     /* Compute delta1 by projecting delta onto normal */
  525.     c = Q3Vector3D_Dot(&newDelta, &normal);
  526.  
  527.     /* Compensate if ray hits backside of face somehow */
  528.     if (c < 0.0) {
  529.         c = -c;
  530.     }
  531.  
  532.     Q3Vector3D_Scale(&normal, c, &delta1);
  533.     Q3Vector3D_Subtract(&newDelta, &delta1, &delta2);
  534.  
  535.     /* Compute deltaPrime from delta1 and negated delta2. This is delta reflected through the normal */
  536.     Q3Vector3D_Negate(&delta2, &delta2);
  537.     Q3Vector3D_Add(&delta1, &delta2, pWorldDelta);
  538.  
  539.     /* Make sure pick ray is normalized */
  540.     Q3Vector3D_Normalize(pWorldDelta, &pWorldRay->direction);
  541.  
  542.     return status;
  543. }
  544.  
  545.  
  546. #pragma mark -
  547.  
  548. /*
  549.  *    Pick_CreateMovingObject
  550.  */
  551. static
  552. TQ3GeometryObject Pick_CreateMovingObject(
  553.             TDocumentPtr        pDoc,
  554.             TQ3Ray3D            *pRay,
  555.             TQ3GroupPosition    *pGroupPosn)
  556. {
  557.     TQ3EllipsoidData    data;
  558.     TQ3GeometryObject    geometry = NULL;
  559.     TQ3AttributeSet        attr     = NULL;
  560.     TQ3GroupObject        group     = NULL;
  561.  
  562.     #define    kDefaultObjectColor    0.8, 0.8, 0.0
  563.  
  564.     data.origin    = pRay->origin;
  565.  
  566.     Q3Vector3D_Set(&data.orientation, 0.0, kBallRadius, 0.0);
  567.     Q3Vector3D_Set(&data.majorRadius, 0.0, 0.0, kBallRadius);
  568.     Q3Vector3D_Set(&data.minorRadius, kBallRadius, 0.0, 0.0);
  569.  
  570.     data.uMin = data.vMin = 0.0f;
  571.     data.uMax = data.vMax = 1.0f;
  572.     data.caps = kQ3EndCapNone;
  573.  
  574.     data.interiorAttributeSet    = NULL;
  575.     data.ellipsoidAttributeSet    = NULL;
  576.  
  577.     attr = Q3AttributeSet_New ();
  578.     if (attr != NULL) {
  579.         TQ3ColorRGB    color = { kDefaultObjectColor };
  580.  
  581.         data.ellipsoidAttributeSet = attr;
  582.         Q3AttributeSet_Add(attr, kQ3AttributeTypeDiffuseColor, &color);
  583.  
  584.         geometry = Q3Ellipsoid_New(&data);
  585.         DEBUG_ASSERT(geometry != NULL, Q3Ellipsoid_New);
  586.         Object_Dispose_NULL(&attr);
  587.     }
  588.  
  589.     /* Put geometry in an unpickable group */
  590.     group = Q3OrderedDisplayGroup_New();
  591.     DEBUG_ASSERT(group != NULL, Q3OrderedDisplayGroup_New);
  592.  
  593.     if (group != NULL) {
  594.         TQ3DisplayGroupState    state;
  595.  
  596.         if (Q3DisplayGroup_GetState(group, &state) == kQ3Success) {
  597.             state &= ~kQ3DisplayGroupStateMaskIsPicked;
  598.  
  599.             Q3DisplayGroup_SetState(group, state);
  600.         }
  601.  
  602.         /* Add geometry to group but don't decrement reference */
  603.         Q3Group_AddObject(group, geometry);
  604.  
  605.         /* Add group to main group */
  606.         *pGroupPosn = Q3Group_AddObject(pDoc->fModel, group);
  607.         Object_Dispose_NULL(&group);
  608.     }
  609.     else {
  610.         /* Error */
  611.         Object_Dispose_NULL(&geometry);
  612.     }
  613.  
  614.     return geometry;
  615. }
  616.  
  617.  
  618. /*
  619.  *    Pick_GetNearestObject
  620.  *
  621.  *    Find and return object hit if it's within minDistance.
  622.  *
  623.  *    In this sample code since we're picking with a single ray
  624.  *    and our moving object is several units wide portions of
  625.  *    this object may pass through other objects because we're
  626.  *    testing with a ray cast from the center of our moving object
  627.  *    rather than near it's outer extents.
  628.  */
  629. static
  630. TQ3Status Pick_GetNearestObject(
  631.     TQ3PickObject        pickObj,
  632.     float                minDistance,
  633.     TQ3GeometryObject    *pGeoHit,
  634.     TQ3GeometryObject    *pPrevGeoHit)
  635. {
  636.     TQ3Status            status    = kQ3Failure;
  637.     TQ3PickDetail        pickDetailValidMask;
  638.     float                distance;
  639.     TQ3GeometryObject    objectHit = NULL;
  640.  
  641.     status = Q3Pick_GetPickDetailValidMask(pickObj, kFirstHit, &pickDetailValidMask);
  642.  
  643.     /* Get distance from ray to intersected geometry */
  644.     if (HAS_Distance(pickDetailValidMask)) {
  645.  
  646.         status = Q3Pick_GetPickDetailData (pickObj, kFirstHit,
  647.                     kQ3PickDetailMaskDistance, &distance);
  648.         DEBUG_ASSERT(status == kQ3Success, Q3Pick_GetPickDetailData);
  649.  
  650.         /* Is this geometry within "minDistance"? */
  651.         if (distance <= minDistance) {
  652.  
  653.             if (HAS_Object(pickDetailValidMask)) {
  654.                 /* Get the object */
  655.                 status = Q3Pick_GetPickDetailData (pickObj, kFirstHit,
  656.                             kQ3PickDetailMaskObject, &objectHit);
  657.  
  658.                 if (objectHit == *pGeoHit) {
  659.                     /* We've hit the same geometry */
  660.                     Object_Dispose_NULL(&objectHit);
  661.                 }
  662.                 else {
  663.                     /* Return new geometry hit */
  664.                     *pPrevGeoHit = *pGeoHit;    /* Need to unhighlight this if non-NULL */
  665.                     *pGeoHit     = objectHit;
  666.                 }
  667.             }
  668.         }
  669.         else {
  670.             *pPrevGeoHit = *pGeoHit;    /* Need to unhighlight this if non-NULL */
  671.             *pGeoHit     = NULL;
  672.         }
  673.     }
  674.  
  675.     return status;
  676. }
  677.  
  678.  
  679. /*
  680.  *    Pick_HighlightObject
  681.  */
  682. static
  683. TQ3Status Pick_HighlightObject(
  684.     TDocumentPtr        pDoc,
  685.     TQ3GeometryObject    geoObj,
  686.     TQ3Boolean            doHighlight)
  687. {
  688.     TQ3Status            status    = kQ3Failure;
  689.     TQ3AttributeSet        attr    = NULL;
  690.     TQ3Switch            isHighlighted = kQ3On;
  691.     TQ3Boolean            doesContain,
  692.                         doUpdate = kQ3False;
  693.  
  694.     DEBUG_ASSERT(pDoc != NULL, Pick_HighlightObject);
  695.     DEBUG_ASSERT(geoObj != NULL, Pick_HighlightObject);
  696.  
  697.     status = Q3Geometry_GetAttributeSet(geoObj, &attr);
  698.     DEBUG_ASSERT(status == kQ3Success, Q3Geometry_GetAttributeSet);
  699.     DEBUG_ASSERT(attr != NULL, Q3Geometry_GetAttributeSet__attr);
  700.  
  701.     if (status == kQ3Success) {
  702.         if (attr != NULL) {
  703.             doesContain = Q3AttributeSet_Contains(
  704.                             attr, kQ3AttributeTypeHighlightState);
  705.  
  706.             /* Add highlight if geometry doesn't contain one */
  707.             if (doHighlight == kQ3True) {
  708.                 if (doesContain == kQ3False) {
  709.                     Q3AttributeSet_Add(
  710.                                 attr, kQ3AttributeTypeHighlightState, &isHighlighted);
  711.                     doUpdate = kQ3True;
  712.                 }
  713.             }
  714.             else {
  715.                 /* Remove highlight if there is one */
  716.                 if (doesContain == kQ3True) {
  717.                     Q3AttributeSet_Clear(
  718.                                 attr, kQ3AttributeTypeHighlightState);
  719.                     doUpdate = kQ3True;
  720.                 }
  721.             }
  722.  
  723.             if (doUpdate == kQ3True) {
  724.                 status = Q3Geometry_SetAttributeSet(geoObj, attr);
  725.                 DEBUG_ASSERT(status == kQ3Success, Q3Geometry_SetAttributeSet);
  726.  
  727.                 Document_Draw(pDoc);
  728.             }
  729.         }
  730.     }
  731.     Object_Dispose_NULL(&attr);
  732.  
  733.     return status;
  734. }
  735.  
  736.  
  737.  
  738. #pragma mark -
  739.  
  740. /*
  741.  *    Pick_CreateLine
  742.  */
  743. TQ3GeometryObject Pick_CreateLine(
  744.             TQ3Point3D            *pXYZ,
  745.             TQ3Vector3D            *pNormal,
  746.             float                scale,
  747.             unsigned long        colorIndex)
  748. {
  749.     TQ3LineData            lineData;
  750.     TQ3GeometryObject    lineObject    = NULL;
  751.     TQ3AttributeSet        attr        = NULL;
  752.     TQ3Vector3D            scaledDir;
  753.  
  754.     DEBUG_ASSERT(colorIndex < sizeof(rgbColors)/sizeof(TQ3ColorRGB), Pick_CreateLine);
  755.  
  756.     if (pNormal != NULL) {
  757.         scaledDir = *pNormal;
  758.         Q3Vector3D_Scale(pNormal, scale, &scaledDir);
  759.  
  760.         /*
  761.          * Create a line representing the normal vector at the point xyz
  762.          */
  763.         lineData.vertices[0].point.x = pXYZ->x;
  764.         lineData.vertices[0].point.y = pXYZ->y;
  765.         lineData.vertices[0].point.z = pXYZ->z;
  766.         lineData.vertices[0].attributeSet = NULL;
  767.  
  768.         lineData.vertices[1].point.x = pXYZ->x + scaledDir.x;
  769.         lineData.vertices[1].point.y = pXYZ->y + scaledDir.y;
  770.         lineData.vertices[1].point.z = pXYZ->z + scaledDir.z;
  771.         lineData.vertices[1].attributeSet = NULL;
  772.  
  773.         attr = Q3AttributeSet_New ();
  774.         if (attr != NULL) {
  775.  
  776.             lineData.lineAttributeSet = attr;
  777.             (void) Q3AttributeSet_Add (attr, kQ3AttributeTypeDiffuseColor,
  778.                         &rgbColors[colorIndex]);
  779.  
  780.             lineObject = Q3Line_New(&lineData);
  781.             Object_Dispose_NULL(&attr);
  782.         }
  783.     }
  784.     else {
  785.         TQ3GroupObject    crossHairGroup = NULL;
  786.  
  787.         crossHairGroup = Q3OrderedDisplayGroup_New();
  788.         if (crossHairGroup != NULL) {
  789.             #define    kHalfLength        (kCylRadius / 2.0)
  790.  
  791.             /*
  792.              * Create a horiztonal line at the point xyz
  793.              */
  794.             lineData.vertices[0].point.x = pXYZ->x - kHalfLength;
  795.             lineData.vertices[1].point.x = pXYZ->x + kHalfLength;
  796.             lineData.vertices[0].point.y =
  797.             lineData.vertices[1].point.y = pXYZ->y;
  798.             lineData.vertices[0].point.z =
  799.             lineData.vertices[1].point.z = pXYZ->z;
  800.             lineData.vertices[0].attributeSet = NULL;
  801.             lineData.vertices[1].attributeSet = NULL;
  802.  
  803.             attr = Q3AttributeSet_New ();
  804.             if (attr != NULL) {
  805.                 lineData.lineAttributeSet = attr;
  806.                 (void) Q3AttributeSet_Add (attr, kQ3AttributeTypeDiffuseColor,
  807.                             &rgbColors[colorIndex]);
  808.  
  809.                 lineObject = Q3Line_New(&lineData);
  810.                 Object_Dispose_NULL(&attr);
  811.  
  812.                 (void) Q3Group_AddObject(crossHairGroup, lineObject);
  813.                 Object_Dispose_NULL(&lineObject);
  814.             }
  815.  
  816.             /*
  817.              * Create a vertical line at the point xyz
  818.              */
  819.             lineData.vertices[0].point.x =
  820.             lineData.vertices[1].point.x = pXYZ->x;
  821.             lineData.vertices[0].point.y = pXYZ->y - kHalfLength;
  822.             lineData.vertices[1].point.y = pXYZ->y + kHalfLength;
  823.             lineData.vertices[0].point.z =
  824.             lineData.vertices[1].point.z = pXYZ->z;
  825.             lineData.vertices[0].attributeSet = NULL;
  826.             lineData.vertices[1].attributeSet = NULL;
  827.  
  828.             attr = Q3AttributeSet_New ();
  829.             if (attr != NULL) {
  830.                 lineData.lineAttributeSet = attr;
  831.                 (void) Q3AttributeSet_Add (attr, kQ3AttributeTypeDiffuseColor,
  832.                             &rgbColors[colorIndex]);
  833.  
  834.                 lineObject = Q3Line_New(&lineData);
  835.                 Object_Dispose_NULL(&attr);
  836.  
  837.                 (void) Q3Group_AddObject(crossHairGroup, lineObject);
  838.                 Object_Dispose_NULL(&lineObject);
  839.             }
  840.  
  841.             lineObject = crossHairGroup;
  842.         }
  843.         else {
  844.             DEBUG_ASSERT(0, "Pick_CreateLine: Q3OrderedDisplayGroup_New failed.");
  845.         }
  846.     }
  847.  
  848.     return lineObject;
  849. }
  850.  
  851.  
  852. /*
  853.  *    Pick_DrawRayAndNormal
  854.  */
  855. TQ3Status Pick_DrawRayAndNormal(
  856.     TDocumentPtr        pDoc,
  857.     TQ3Ray3D            *pTestRay,
  858.     TQ3GroupObject        normalGroup,
  859.     unsigned long        numHits)
  860. {
  861.     TQ3Status            status;
  862.     TQ3PickDetail        pickDetailValidMask;
  863.     TQ3Point3D            xyzPoint;
  864.     TQ3Vector3D            normal;
  865.     float                distance;
  866.     TQ3GeometryObject    normalObject    = NULL,
  867.                         rayObject        = NULL;
  868.     TQ3Object            tempObject        = NULL;
  869.     TQ3GroupPosition    normalPosn        = NULL,
  870.                         rayPosn            = NULL,
  871.                         normalGroupPosn    = NULL;
  872.     TQ3Boolean            hasXYZandNormal,
  873.                         hasXYZOnly;
  874.  
  875.     if (numHits >= 1) {
  876.         status = Q3Pick_GetPickDetailValidMask(gPick, kFirstHit, &pickDetailValidMask);
  877.         DEBUG_ASSERT(status != kQ3Failure, Q3Pick_GetPickDetailValidMask);
  878.  
  879.         hasXYZOnly =  HAS_XYZ(pickDetailValidMask)
  880.                                 ? kQ3True
  881.                                 : kQ3False;
  882.         if (hasXYZOnly) {
  883.             status = Q3Pick_GetPickDetailData (gPick, kFirstHit,
  884.                         kQ3PickDetailMaskXYZ, &xyzPoint);
  885.             DEBUG_ASSERT(status != kQ3Failure, Q3Pick_GetPickDetailData);
  886.         }
  887.  
  888.         hasXYZandNormal = (HAS_Normal(pickDetailValidMask)  &&  hasXYZOnly)
  889.                                 ? kQ3True
  890.                                 : kQ3False;
  891.         if (hasXYZandNormal) {
  892.             hasXYZOnly = kQ3False;
  893.  
  894.             status = Q3Pick_GetPickDetailData (gPick, kFirstHit,
  895.                         kQ3PickDetailMaskNormal, &normal);
  896.             DEBUG_ASSERT(status != kQ3Failure, Q3Pick_GetPickDetailData);
  897.         }
  898.     }
  899.     else {
  900.         hasXYZandNormal = kQ3False;
  901.         hasXYZOnly      = kQ3False;
  902.     }
  903.  
  904.     if (hasXYZandNormal == kQ3True) {
  905.         /* 
  906.          * Add representation of normal and ray to
  907.          * main normal group then remove them again
  908.          */
  909.         normalObject = Pick_CreateLine(
  910.                                     &xyzPoint,
  911.                                     &normal,
  912.                                     2.0 * kCylRadius,
  913.                                     k_Magenta);
  914.         if (normalObject == NULL) {
  915.             return kQ3Failure;
  916.         }
  917.  
  918.         /* Add normal object to normalGroup */
  919.         normalPosn = Q3Group_AddObject(normalGroup, normalObject);
  920.         Object_Dispose_NULL(&normalObject);
  921.     }
  922.     else
  923.     if (hasXYZOnly == kQ3True) {
  924.         /* 
  925.          * Add representation of intersection cross hairs to
  926.          * main normal group then remove them again
  927.          */
  928.         normalObject = Pick_CreateLine(
  929.                                     &xyzPoint,
  930.                                     NULL,
  931.                                     1.0,
  932.                                     k_Cyan);
  933.         if (normalObject == NULL) {
  934.             return kQ3Failure;
  935.         }
  936.  
  937.         /* Add normal object to normalGroup */
  938.         normalPosn = Q3Group_AddObject(normalGroup, normalObject);
  939.         Object_Dispose_NULL(&normalObject);
  940.     }
  941.  
  942.  
  943.     /* Make a line from ray's origin to intersection point */
  944.     if (HAS_Distance(pickDetailValidMask)) {
  945.         status = Q3Pick_GetPickDetailData (gPick, kFirstHit,
  946.                     kQ3PickDetailMaskDistance, &distance);
  947.         DEBUG_ASSERT(status == kQ3Success, Q3Pick_GetPickDetailData);
  948.     }
  949.  
  950.     /* Scale test ray so we can see it */
  951.     rayObject = Pick_CreateLine(
  952.                             &pTestRay->origin,
  953.                             &pTestRay->direction,
  954.                             distance,
  955.                             (numHits == 1)
  956.                                 ? k_Green
  957.                                 : k_Red);
  958.     if (rayObject == NULL) {
  959.         Object_Dispose_NULL(&normalObject);
  960.         return kQ3Failure;
  961.     }
  962.  
  963.     /* Add ray object to normalGroup */
  964.     rayPosn = Q3Group_AddObject(normalGroup, rayObject);
  965.     Object_Dispose_NULL(&rayObject);
  966.  
  967.     /* Add normalGroup to model */
  968.     normalGroupPosn = Q3Group_AddObject(pDoc->fModel, normalGroup);
  969.  
  970.     /* Draw model with added lines */
  971.     Document_Draw(pDoc);
  972.  
  973.     /* Remove normal and ray objects from normalGroup, normalGroup from model */
  974.     if (normalPosn != NULL) {
  975.         tempObject = Q3Group_RemovePosition (normalGroup, normalPosn);
  976.         Object_Dispose_NULL(&tempObject);
  977.     }
  978.  
  979.     if (rayPosn != NULL) {
  980.         tempObject = Q3Group_RemovePosition (normalGroup, rayPosn);
  981.         Object_Dispose_NULL(&tempObject);
  982.     }
  983.  
  984.     if (normalGroupPosn != NULL) {
  985.         tempObject = Q3Group_RemovePosition (pDoc->fModel, normalGroupPosn);
  986.         Object_Dispose_NULL(&tempObject);
  987.     }
  988.  
  989.     return kQ3Success;
  990. }
  991.